﻿using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Data;
using System.Data.SqlClient;
using HQPad.Models;
using HQPad.DAL;

namespace HQPad.Services
{
    public enum RequirementCombination {
        AND,
        OR
    }

    /// <summary>
    /// Possible skill types.
    /// </summary>
    public enum SkillType
    {
        Martial,
        Archer,
        Physical,
        Medical,
        Magic,
        Power,
        Subterfuge,
        Armour,
        Potion,
        Ki,
        Adrenal,
        Psi,
        Royal,
        Other,
        Special128,
        Special256
    }

    public interface ISkillService
    {
        Skill GetSkill(int skillId);
        Skill GetSkill(int ruleSetId, string slug);
        IQueryable<Skill> GetRuleSkills(int ruleSetId);
        void Add(Skill skill);
        void UpdateSkill(Skill skill);
        void DeleteSkill(int skillId);
        void DeleteSkill(Skill skill);
        IEnumerable<SkillCost> GetSkillCosts (int skillId);
        void UpdateSkillCost(SkillCost cost);
        IEnumerable<SkillRequirement> GetSkillRequirements(int skillId);
        //Skill AddSkill(int ruleSetId, string name, int? maxRanks, int[] human, int[] elf, int[] orc,
        //    int[] humanMod, int[] elfMod, int[] orcMod, bool isBasic, string[] requiredSkillSlugs,
        //    string typeOfSkill);
        Skill AddSkill(int ruleSetId, string name, int? maxRanks, bool isBasic, string slug, SkillType type);
        SkillRequirement GetSkillRequirement(int skillRequirementId);
        void DeleteRequirement(SkillRequirement requirement);
        SkillRequirement CreateRequirement(int parentSkillId, int? skillId, int? guildId, int? minRank, bool primaryOnly, int? quantity, RequirementCombination combination, int? guildLevel);
        SkillRequirement CreateRequirement(Skill parentSkill, int? skillId, int? guildId, int? minRank, bool primaryOnly, int? quantity, RequirementCombination combination, int? guildLevel);
        IEnumerable<PointsTable> GetPointsTables(int ruleSetId);
        string CreateSkillSlug(string skillName);
        string GetSkillSlugByLegacyName(int rulesId, string legacyName);
        IEnumerable<SkillView> GetAllowedSkills(int rulesId, string skills, string ranks, int guildLevel, string primaryGuidSlug, string secondaryGuidSlug, string tertiaryGuidSlug, int characterRank, int pointsTableId, bool showAllSkills);
        //NameValueCollection GetSkillChoices(Skill skill);
    }

    public class SkillService : ISkillService
    {
        private HQContext context = null;

        public SkillService(IUnitOfWork context)
        {
            this.context = (HQContext)context;
        }

        public void Add(Skill skill)
        {
            context.Skills.Add(skill);
        }

        public void UpdateSkill(Skill skill)
        {
            context.Entry(skill).State = EntityState.Modified;
        }

        public Skill GetSkill(int skillId)
        {
            return context.Skills.Find(skillId);
        }

        public Skill GetSkill(int ruleSetId, string slug)
        {
            return
                (from s in context.Skills
                 where s.Slug == slug.ToLower() && s.RuleSetID == ruleSetId
                 select s)
                .FirstOrDefault();
        }

        public string GetSkillSlugByLegacyName(int ruleSetId, string legacyName)
        {
            return
                (from s in context.Skills
                 where s.LegacyName == legacyName.ToLower() && s.RuleSetID == ruleSetId
                 select s.Slug)
                .FirstOrDefault();
        }

        public IQueryable<Skill> GetRuleSkills(int ruleSetId)
        {
            return context.Skills.Where(s => s.RuleSetID == ruleSetId).OrderBy(a => a.Name);
        }

        public void DeleteSkill(int skillId)
        {
            Skill skill = GetSkill(skillId);
            context.Skills.Remove(skill);
        }

        public void DeleteSkill(Skill skill)
        {
            context.Skills.Remove(skill);
        }

        /// <summary>
        /// Get the skill costs for a skill
        /// ordered by points table race and class.
        /// </summary>
        /// <param name="skillId"></param>
        /// <returns></returns>
        public IEnumerable<SkillCost> GetSkillCosts(int skillId)
        {
            return 
                (from c in context.SkillCosts
                 where c.SkillID == skillId
                 orderby c.Table.Race, c.Table.Class
                 select c);
        }

        public void UpdateSkillCost(SkillCost cost)
        {
            context.Entry(cost).State = EntityState.Modified;
        }

        public IEnumerable<SkillRequirement> GetSkillRequirements(int skillId)
        {
            return
                (from r in context.SkillRequirements
                where r.ParentSkillID == skillId
                select r);
        }

        #region AddSkill
        /// <summary>
        /// Add a basic skill.
        /// No sub table relationships are created (points etc).
        /// </summary>
        /// <param name="ruleSetId">The id of the rule set for the skill.</param>
        /// <param name="name">The skill name</param>
        /// <param name="maxRanks">Optional maximum number of ranks.</param>
        /// <param name="isBasic">Whether the skill is a basic skill.</param>
        /// <param name="slug">The unique skill slug. This is not checked for uniqueness.</param>
        /// <param name="type">The type of the skill.</param>
        /// <returns>The skill created.</returns>
        public Skill AddSkill(int ruleSetId, string name, int? maxRanks, bool isBasic, string slug, SkillType type)
        {
            var skill = new Skill
            {
                Name = name,
                Slug = slug,
                Deleted = false,
                LastUpdated = DateTime.Now,
                LastUpdatedByUserID = 1,
                MaxRanks = maxRanks,
                RuleSetID = ruleSetId,
                IsBasic = isBasic,
                TypeOfSkill = Enum.GetName(typeof(SkillType), type)
            };

            context.Skills.Add(skill);
            return skill;
        }

        #region Obsolete
        /// <summary>
        /// Create a new skill.
        /// </summary>
        /// <param name="context"></param>
        /// <param name="name"></param>
        /// <param name="maxRanks"></param>
        /// <param name="human"></param>
        /// <param name="elf"></param>
        /// <param name="orc"></param>
        /// <param name="humanMod"></param>
        /// <param name="elfMod"></param>
        /// <param name="orcMod"></param>
        /// <param name="isBasic"></param>
        /// <returns></returns>
        [Obsolete("Adding a skill with this all in one function is obsolete. Add individual elements instead")]
        public Skill AddSkill(int ruleSetId, string name, int? maxRanks,
            int[] human, int[] elf, int[] orc,
            int[] humanMod, int[] elfMod, int[] orcMod,
            bool isBasic, string[] requiredSkills,
            string typeOfSkill)
        {
            var skill = new Skill
            {
                Name = name,
                Slug = name.ToLower().Replace(" ", "-"),
                Deleted = false,
                LastUpdated = DateTime.Now,
                LastUpdatedByUserID = 1,
                MaxRanks = maxRanks,
                RuleSetID = ruleSetId,
                IsBasic = isBasic,
                TypeOfSkill = typeOfSkill
            };
            context.Skills.Add(skill);
            context.SaveChanges();

            var costs = new List<SkillCost>
            {
                new SkillCost() {
                    Available = true, Cost = human[0], Factor = humanMod[0], PointsTableID = 1, SkillID = skill.SkillID },
                new SkillCost() {
                    Available = true, Cost = human[1], Factor = humanMod[1], PointsTableID = 2, SkillID = skill.SkillID },
                new SkillCost() {
                    Available = true, Cost = human[2], Factor = humanMod[2], PointsTableID = 3, SkillID = skill.SkillID },
                new SkillCost() {
                    Available = true, Cost = human[3], Factor = humanMod[3], PointsTableID = 4, SkillID = skill.SkillID },
                new SkillCost() {
                    Available = true, Cost = elf[0], Factor = elfMod[0], PointsTableID = 5, SkillID = skill.SkillID },
                new SkillCost() {
                    Available = true, Cost = elf[1], Factor = elfMod[1], PointsTableID = 6, SkillID = skill.SkillID },
                new SkillCost() {
                    Available = true, Cost = elf[2], Factor = elfMod[2], PointsTableID = 7, SkillID = skill.SkillID },
                new SkillCost() {
                    Available = true, Cost = elf[3], Factor = elfMod[3], PointsTableID = 8, SkillID = skill.SkillID },
                new SkillCost() {
                    Available = true, Cost = orc[0], Factor = orcMod[0], PointsTableID = 9, SkillID = skill.SkillID },
                new SkillCost() {
                    Available = true, Cost = orc[1], Factor = orcMod[1], PointsTableID = 10, SkillID = skill.SkillID },
                new SkillCost() {
                    Available = true, Cost = orc[2], Factor = orcMod[2], PointsTableID = 11, SkillID = skill.SkillID },
                new SkillCost() {
                    Available = true, Cost = orc[3], Factor = orcMod[3], PointsTableID = 12, SkillID = skill.SkillID },
            };

            if (requiredSkills != null)
            {
                foreach (string slug in requiredSkills)
                {
                    Skill reqSkill = GetSkill(ruleSetId, slug);
                    context.SkillRequirements.Add(new SkillRequirement() { Guild = null, MinRank = null, ParentSkillID = skill.SkillID, RequiredSkill = reqSkill, Combination = "AND", QuantityRequired = 1 });
                }
            }

            costs.ForEach(s => context.SkillCosts.Add(s));
            context.SaveChanges();

            return skill;
        }
        #endregion

        #endregion

        #region GetSkillRequirement
        /// <summary>Get a skill requirement by primary key id.</summary>
        /// <param name="skillRequirementId">Primary key id</param>
        /// <returns></returns>
        public SkillRequirement GetSkillRequirement(int skillRequirementId)
        {
            return context.SkillRequirements.Find(skillRequirementId);
        }
        #endregion

        #region DeleteRequirement
        /// <summary>Mark a skill requirement for deletion.</summary>
        /// <param name="requirement">The skill requirement to delete.</param>
        public void DeleteRequirement(SkillRequirement requirement)
        {
            context.SkillRequirements.Remove(requirement);
        }
        #endregion

        #region CreateRequirement
        /// <summary>Create a new skill requirement.</summary>
        /// <param name="parentSkillId">ID of the parent skill.</param>
        /// <param name="skillId">Any required skill.</param>
        /// <param name="guildId">Any required guild</param>
        /// <param name="minRank">Any required minimum rank.</param>
        /// <param name="primaryOnly">Whether the guild requiement is primary only.</param>
        /// <param name="quantity">The optional quantity of ranks required.</param>
        /// <param name="combination">How the requirement combines.</param>
        /// <param name="guildLevel">The level in the guild required.</param>
        /// <returns>The requirement created.</returns>
        public SkillRequirement CreateRequirement(int parentSkillId, int? skillId, int? guildId, int? minRank, bool primaryOnly, int? quantity, RequirementCombination combination, int? guildLevel)
        {
            SkillRequirement requirement = new SkillRequirement() { ParentSkillID = parentSkillId };
                
            if (skillId.HasValue)
                requirement.RequiredSkillID =  skillId.Value;

            if (guildId.HasValue)
                requirement.GuildID = guildId.Value;

            if (minRank.HasValue)
                requirement.MinRank = minRank.Value;

            requirement.PrimaryGuildOnly = primaryOnly;

            if (quantity.HasValue)
                requirement.QuantityRequired = quantity;

            if (guildLevel.HasValue)
                requirement.GuildLevel = guildLevel.Value;

            string combo = Enum.GetName(typeof(RequirementCombination), combination);

            requirement.Combination = combo;

            context.SkillRequirements.Add(requirement);

            return requirement;
        }

        /// <summary>Create a new skill requirement.</summary>
        /// <param name="parentSkill">The parent skill.</param>
        /// <param name="skillId">Any required skill.</param>
        /// <param name="guildId">Any required guild</param>
        /// <param name="minRank">Any required minimum rank.</param>
        /// <param name="primaryOnly">Whether the guild requiement is primary only.</param>
        /// <param name="quantity">The optional quantity of ranks required.</param>
        /// <param name="combination">How the requirement combines.</param>
        /// <param name="guildLevel">The level in the guild required.</param>
        /// <returns>The requirement created.</returns>
        public SkillRequirement CreateRequirement(Skill parentSkill, int? skillId, int? guildId, int? minRank, bool primaryOnly, int? quantity, RequirementCombination combination, int? guildLevel)
        {
            if (parentSkill.Requirements == null)
                parentSkill.Requirements = new List<SkillRequirement>();

            SkillRequirement requirement = new SkillRequirement();

            if (skillId.HasValue)
                requirement.RequiredSkillID = skillId.Value;

            if (guildId.HasValue)
                requirement.GuildID = guildId.Value;

            if (minRank.HasValue)
                requirement.MinRank = minRank.Value;

            requirement.PrimaryGuildOnly = primaryOnly;

            if (quantity.HasValue)
                requirement.QuantityRequired = quantity;

            if (guildLevel.HasValue)
                requirement.GuildLevel = guildLevel.Value;

            string combo = Enum.GetName(typeof(RequirementCombination), combination);

            requirement.Combination = combo;

            parentSkill.Requirements.Add(requirement);

            return requirement;
        }
        #endregion

        #region GetPointsTables
        /// <summary>
        /// Get the points tables ordered by race and class.
        /// </summary>
        /// <param name="ruleSetId"></param>
        /// <returns></returns>
        public IEnumerable<PointsTable> GetPointsTables(int ruleSetId)
        {
            return context.PointsTables
                .Where(p => p.RuleSetID == ruleSetId)
                .OrderBy(p => p.Race).ThenBy(p => p.Class);
        }
        #endregion

        #region CreateSkillSlug
        /// <summary>Create a skill slug from a skill name.</summary>
        /// <param name="skillName">The name of the skill</param>
        /// <returns>The standard format skill slug.</returns>
        public string CreateSkillSlug(string skillName)
        {
            return skillName
                .Trim()
                .Replace("_", "")
                .Replace("-", "")
                .Replace("&", "")
                .Replace("+", "")
                .Replace(",", "")
                .Replace(".", "")
                .Replace(" ", "-")
                .Replace("--", "-")
                .Replace("--", "-")
                .ToLower();
        }
        #endregion

        #region GetAllowedSkills
        /// <summary>
        /// Get a list of untracked skill entity objects that represent a list
        /// of allowed skills for a character.
        /// </summary>
        /// <param name="rulesId"></param>
        /// <param name="skills"></param>
        /// <param name="ranks"></param>
        /// <param name="guildLevel"></param>
        /// <param name="primaryGuidSlug"></param>
        /// <param name="secondaryGuidSlug"></param>
        /// <param name="tertiaryGuidSlug"></param>
        /// <param name="characterRank"></param>
        /// <returns></returns>
        public IEnumerable<SkillView> GetAllowedSkills(int rulesId, string skills, string ranks, int guildLevel, string primaryGuidSlug, string secondaryGuidSlug, string tertiaryGuidSlug, int characterRank, int pointsTableId, bool showAllSkills)
        {
            IEnumerable<SkillView> allowedSkills = null;

            allowedSkills = context.Database.SqlQuery<SkillView>(
                "hqpad_character_allowed_skills @rulesId, @skills, @ranks, @guildLevel, @primaryGuild, @secondaryGuild, @tertiaryGuild, @characterRank, @pointsTableId, @showAllSkills",
                new SqlParameter("@rulesId", rulesId),
                new SqlParameter("@skills", skills),
                new SqlParameter("@ranks", ranks),
                new SqlParameter("@guildLevel", guildLevel),
                new SqlParameter("@primaryGuild", primaryGuidSlug),
                new SqlParameter("@secondaryGuild", secondaryGuidSlug),
                new SqlParameter("@tertiaryGuild", tertiaryGuidSlug),
                new SqlParameter("@characterRank", characterRank),
                new SqlParameter("@pointsTableId", pointsTableId),
                new SqlParameter("@showAllSkills", showAllSkills ? 1 : 0));

            return allowedSkills;
        }
        #endregion


    }
}